import pygame as pg
import pygame.transform as pt
import sys
import random
import pickle
import os
import time
from pygame.sprite import Sprite


class GameStat:
    PLAY = 'PLAY'
    MSG = 'MSG'
    SHOP = 'SHOP'
    BOX = 'BOX'


class Config:
    WIDTH = 1200
    HEIGTH = 800
    step = 8
    alienstep = 6
    shipblood = 2
    max_alien = 15
    score = 0
    color = (0, 0, 0)


class Ship(Sprite):
    def __init__(self, mygame_game):
        self.screen = mygame_game.screen
        self.blood = Config.shipblood
        self.screen_rect = mygame_game.screen.get_rect()
        self.image = pg.image.load('Ethan/game_image/blue_ship.png')
        self.rect = self.image.get_rect()
        self.rect.midbottom = self.screen_rect.midbottom
        self.moving_left = False
        self.moving_right = False
        self.moving_up = False
        self.moving_down = False

    def update(self):
        if self.moving_left:
            if self.rect.bottomleft[0] > Config.step:
                self.rect.x -= Config.step
        if self.moving_right:
            if self.rect.bottomright[0] < Config.WIDTH-Config.step:
                self.rect.x += Config.step
        if self.moving_down:
            if self.rect.bottomleft[1] < Config.HEIGTH-Config.step:
                self.rect.y += Config.step
        if self.moving_up:
            if self.rect.topleft[1] > Config.step:
                self.rect.y -= Config.step

    def blitme(self):
        self.screen.blit(self.image, self.rect)


class ShipBullet(Sprite):
    def __init__(self, mygame_game, *groups):
        super().__init__(*groups)
        self.game = mygame_game
        self.screen = mygame_game.screen
        self.screen_rect = mygame_game.screen.get_rect()
        self.image = pg.image.load('Ethan/game_image/blue_bullet.png')
        self.rect = self.image.get_rect()

    def draw(self):
        self.screen.blit(self.image, self.rect)

    def update(self):
        self.rect.y -= Config.step
        if self.rect.y < 0:
            self.game.shipbullets.remove(self)


class Alien(Sprite):
    def __init__(self, mygame_game, *groups):
        super().__init__(*groups)
        self.game = mygame_game
        self.screen = mygame_game.screen
        self.screen_rect = mygame_game.screen.get_rect()
        self.blood = 1
        if self.blood == 1 and random.random() > 0.75:
            self.blood += 1
        if self.blood == 1:
            self.image = pg.image.load('Ethan/game_image/yellow_alien.png')
            self.rect = self.image.get_rect()
            self.max_blood = 1
        else:
            self.image = pg.image.load('Ethan/game_image/blue_alien.png')
            self.rect = self.image.get_rect()
            self.max_blood = 2
        self.bomb_image = pg.image.load('Ethan/game_image/bomb.png')
        self.bomb_rect = self.image.get_rect()
        self.rect.x = random.randint(0, Config.WIDTH)

    def draw(self):
        self.screen.blit(self.image, self.rect)

    def draw_bomb(self):
        self.bomb_rect.center = self.rect.center
        self.screen.blit(self.bomb_image, self.bomb_rect)

    def update(self):
        self.rect.y += Config.alienstep
        if self.rect.y > Config.HEIGTH:
            self.game.aliens.remove(self)
            self.game.alien_number -= 1


class Shop(Sprite):
    def __init__(self, mygame_game, *groups):
        super().__init__(*groups)
        self.screen = mygame_game.screen
        self.screen_rect = mygame_game.screen.get_rect()
        if os.path.exists('score'):
            with open('score', 'rb') as f:
                self.current_score = pickle.load(f)
        else:
            self.current_score = 0
        self.red_ship, self.red_ship_rect = init_image(
            'Ethan/game_image/red_ship.png', 1.5, 80, 80)
        self.purple_bullet, self.purple_bullet_rect = init_image(
            'Ethan/game_image/purple_bullet.png', 1.5, 160, 80)
        self.back, self.back_rect = init_back(self.screen_rect)
        self.show_time = None

    def score_ltp(self):
        self.score_small_font = pg.font.SysFont(None, 80)
        self.score_small = self.score_small_font.render(
            'score less than price', False, pg.Color('red'))
        self.score_small_rect = self.score_small.get_rect()
        self.score_small_rect.center = self.screen_rect.center
        self.screen.blit(self.score_small, self.score_small_rect)

    def draw(self):
        self.current_score_font = pg.font.SysFont(None, 80)
        self.current = self.current_score_font.render(
            str(self.current_score), False, pg.Color('red'))
        self.current_score_rect = self.current.get_rect()
        self.current_score_rect.topleft = (
            self.screen_rect.topleft[0]+20, self.screen_rect.topleft[1]+20)
        self.screen.blit(self.current, self.current_score_rect)
        self.screen.blit(self.back, self.back_rect)
        self.screen.blit(self.red_ship, self.red_ship_rect)
        self.screen.blit(self.purple_bullet, self.purple_bullet_rect)
        if self.show_time is not None:
            if time.time()-self.show_time < 1.5:
                self.score_ltp()
            else:
                self.show_time = None


class Box(Sprite):
    def __init__(self, mygame_game):
        self.game = mygame_game
        self.screen = mygame_game.screen
        self.screen_rect = mygame_game.screen.get_rect()
        self.back, self.back_rect = init_back(self.screen_rect)
        self.eqpmt_x = 260
        self.box_dict = {'ship': None}

    def draw(self):
        self.screen.blit(self.back, self.back_rect)
        self.tips_font = pg.font.SysFont(None, 80)
        self.tips = self.tips_font.render('My box:', False, pg.Color('yellow'))
        self.tips_rect = self.tips.get_rect()
        self.tips_rect.topleft = (135, 120)
        pg.draw.line(self.screen,pg.Color('yellow'),(30,560),(1170,560),8)
        self.screen.blit(self.tips, self.tips_rect)
        for key, value in self.game.equipment_dict.items():
            self.eq_image = pt.scale_by(pg.image.load(
                'Ethan/game_image/'+key+'.png'), 1.5)
            self.eq_rect = self.eq_image.get_rect()
            self.eq_rect.topleft = (self.eqpmt_x, 200)
            self.screen.blit(self.eq_image, self.eq_rect)
            self.eqpmt_x += self.eq_rect.topleft[0]-self.eq_rect.topleft[0]


class Game:
    def __init__(self):
        pg.init()
        self.screen = pg.display.set_mode((Config.WIDTH, Config.HEIGTH))
        pg.display.set_caption('game')
        self.ship = Ship(self)                # 玩家
        self.shop = Shop(self)                # 商店
        self.box = Box(self)
        self.shipbullets = pg.sprite.Group()  # 飞船子弹
        self.aliens = pg.sprite.Group()       # 外星人
        self.alienbullets = pg.sprite.Group()  # 外星人发射的子弹
        self.alien_number = 0
        self.score = 0                        # 分数
        self.font = pg.font.SysFont(None, 50)
        self.red_coin = pg.image.load(
            'Ethan/game_image/red_bullet_goods.png')
        self.screen_rect = self.screen.get_rect()
        self.bg_music = pg.mixer.Sound('Ethan/game_sound/megaWall.mp3')
        self.score_misic = pg.mixer.Sound('Ethan/game_sound/score.wav')
        self.bg_c = None
        self.is_playmusic = False
        self.playmusic_event = True
        pg.key.set_repeat(100,100)
        if os.path.exists('eqpmt_dict'):
            with open('eqpmt_dict', 'rb') as f:
                self.equipment_dict = pickle.load(f)
        else:
            self.equipment_dict = {}
        self.game_state = GameStat.MSG

    def _check_events(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                saveAndExit(self.equipment_dict, self.shop.current_score)
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_LEFT:
                    self.ship.moving_left = True
                if event.key == pg.K_RIGHT:
                    self.ship.moving_right = True
                if event.key == pg.K_UP:
                    self.ship.moving_up = True
                if event.key == pg.K_DOWN:
                    self.ship.moving_down = True
                if event.key == pg.K_SPACE:
                    bullet = ShipBullet(self)
                    bullet.rect.midbottom = self.ship.rect.midtop
                    self.shipbullets.add(bullet)
            if event.type == pg.KEYUP:
                if event.key == pg.K_LEFT:
                    self.ship.moving_left = False
                if event.key == pg.K_RIGHT:
                    self.ship.moving_right = False
                if event.key == pg.K_UP:
                    self.ship.moving_up = False
                if event.key == pg.K_DOWN:
                    self.ship.moving_down = False

    def blit(self):
        self.ship.blitme()

    def draw_box(self):
        self.box.draw()
        for event in pg.event.get():
            if event.type == pg.QUIT:
                saveAndExit(self.equipment_dict, self.shop.current_score)
            if event.type == pg.MOUSEBUTTONDOWN:
                mouse_x, mouse_y = pg.mouse.get_pos()
                if self.box.back_rect.collidepoint(mouse_x, mouse_y):
                    self.game_state = GameStat.MSG

    def add_goods(self, goods_name: str, price: int):
        if self.shop.current_score >= price:
            self.equipment_dict[goods_name] = self.equipment_dict.get(
                goods_name, 0)+1
            self.shop.current_score -= price
            self.shop.draw()
            print(self.equipment_dict)
        else:
            self.shop.show_time = time.time()

    def draw_shop(self):
        self.shop.draw()
        for event in pg.event.get():
            if event.type == pg.QUIT:
                saveAndExit(self.equipment_dict, self.shop.current_score)
            if event.type == pg.MOUSEBUTTONDOWN:
                mouse_x, mouse_y = pg.mouse.get_pos()
                if self.shop.back_rect.collidepoint(mouse_x, mouse_y):
                    self.game_state = GameStat.MSG
                if self.shop.red_ship_rect.collidepoint(mouse_x, mouse_y):
                    self.add_goods('red_ship', 20)

    def draw_message(self):
        play_font = pg.font.SysFont(None, 80)
        play = play_font.render('play', False, pg.Color('red'))
        play_rect = play.get_rect()
        play_rect.topright = (
            self.screen_rect.centerx-50, self.screen_rect.centery)
        shop_font = pg.font.SysFont(None, 80)
        shop = shop_font.render('shop', False, pg.Color('red'))
        shop_rect = shop.get_rect()
        shop_rect.topleft = (
            self.screen_rect.centerx+50, self.screen_rect.centery)
        box_font = pg.font.SysFont(None, 80)
        box = box_font.render('box', False, pg.Color('red'))
        box_rect = box.get_rect()
        box_rect.bottomright = (
            self.screen_rect.bottomright[0]-20, self.screen_rect.bottomright[1]-20)
        self.screen.blit(play, play_rect)
        self.screen.blit(shop, shop_rect)
        self.screen.blit(box, box_rect)
        pg.mixer_music.set_volume(0.3)
        for event in pg.event.get():
            if event.type == pg.QUIT:
                saveAndExit(self.equipment_dict, self.shop.current_score)
            if event.type == pg.MOUSEBUTTONDOWN:
                mouse_x, mouse_y = pg.mouse.get_pos()
                if play_rect.collidepoint(mouse_x, mouse_y):
                    self.ship = Ship(self)
                    self.score = 0
                    self.game_state = GameStat.PLAY
                if shop_rect.collidepoint(mouse_x, mouse_y):
                    self.game_state = GameStat.SHOP
                if box_rect.collidepoint(mouse_x, mouse_y):
                    self.game_state = GameStat.BOX
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_F1:
                    if self.playmusic_event:
                        self.playmusic_event = False
                        self.bg_c.stop()
                    else:
                        self.playmusic_event = True
                        self.bg_c = self.bg_music.play(-1)

    def run_code(self):
        self.blit()
        self.ship.update()
        self.shipbullets.update()
        self.shipbullets.draw(self.screen)
        self.aliens.update()
        self.aliens.draw(self.screen)
        if self.alien_number < Config.max_alien and random.randint(1, 100) > 95:
            self.aliens.add(Alien(self))
            self.alien_number += 1
        collidelist_ship_alien = pg.sprite.spritecollide(
            self.ship, self.aliens, dokill=True)
        for spirt in collidelist_ship_alien:
            self.alien_number -= 1
            self.ship.blood -= 1
            if self.ship.blood <= 0:
                self.shop.current_score += self.score
                save_score(self.shop.current_score)
        collidelist_shipbullet_alien = pg.sprite.groupcollide(
            self.shipbullets, self.aliens, True, False)
        for key, value in collidelist_shipbullet_alien.items():
            for ali in value:
                ali.draw_bomb()
                ali.blood -= 1
                if ali.blood <= 0:
                    self.aliens.remove(ali)
                    self.score += ali.max_blood
                    self.score_misic.play(0)
            self.alien_number -= 1
        score_suf = self.font.render(
            'Score:'+str(self.score), False, pg.Color('red'))
        score_rect = score_suf.get_rect()
        score_rect.topleft = (10, 10)
        self.screen.blit(score_suf, score_rect)
        red_coin_rect = self.red_coin.get_rect()
        red_coin_rect.topleft = score_rect.topright
        red_coin_rect.centery = score_rect.centery
        self.screen.blit(self.red_coin, red_coin_rect)

    def over_code(self):
        self.aliens.empty()
        overfont = pg.font.SysFont(None, 100)
        self.game_over = overfont.render(
            'Game Over!', False, pg.Color('red'))
        overrect = self.game_over.get_rect()
        overrect.midbottom = (self.screen_rect.centerx,
                              self.screen_rect.centery-100)
        self.screen.blit(self.game_over, overrect)
        messagefont = pg.font.SysFont(None, 60)
        self.message = messagefont.render(
            'your score is:'+str(self.score), False, pg.Color('red'))
        self.message_rect = self.message.get_rect()
        self.message_rect.midtop = (
            self.screen_rect.centerx, self.screen_rect.centery-80)
        self.screen.blit(self.message, self.message_rect)
        self.playagain_font = pg.font.SysFont(None, 70)
        self.playagain = self.playagain_font.render(
            'play again', False, pg.Color('red'))
        self.playagain_rect = self.playagain.get_rect()
        self.playagain_rect.topright = (
            self.screen_rect.centerx-80, self.screen_rect.centery+20)
        self.screen.blit(self.playagain, self.playagain_rect)
        self.page_font = pg.font.SysFont(None, 70)
        self.page = self.page_font.render('main page', False, pg.Color('red'))
        self.page_rect = self.page.get_rect()
        self.page_rect.topleft = (
            self.screen_rect.centerx+100, self.screen_rect.centery+15)
        self.screen.blit(self.page, self.page_rect)
        for event in pg.event.get():
            if event.type == pg.QUIT:
                saveAndExit(self.equipment_dict, self.shop.current_score)
            if event.type == pg.MOUSEBUTTONDOWN:
                mouse_x, mouse_y = pg.mouse.get_pos()
                if self.playagain_rect.collidepoint(mouse_x, mouse_y):
                    self.score = 0
                    self.ship = Ship(self)
                if self.page_rect.collidepoint(mouse_x, mouse_y):
                    self.game_state = GameStat.MSG

    def run(self):
        clock = pg.time.Clock()
        while True:
            self.screen.fill(Config.color)
            if self.game_state == GameStat.PLAY:
                if self.is_playmusic:
                    self.is_playmusic = False
                    self.bg_c.fadeout(2000)
                if self.ship.blood > 0:
                    self._check_events()
                    self.run_code()
                else:
                    self.over_code()
            if self.game_state == GameStat.MSG:
                if self.playmusic_event and not self.is_playmusic:
                    self.bg_c = self.bg_music.play(-1, 0)
                    self.is_playmusic = True
                self.draw_message()
            if self.game_state == GameStat.SHOP:
                self.draw_shop()
            if self.game_state == GameStat.BOX:
                self.draw_box()
            pg.display.flip()
            pg.display.update()
            clock.tick(20)


def init_back(screen_rect):
    back_font = pg.font.SysFont(None, 80)
    back = back_font.render('back', False, pg.Color('red'))
    back_rect = back.get_rect()
    back_rect.topright = (
        screen_rect.topright[0]-20, screen_rect.topright[1]+20)
    return back, back_rect


def init_image(name: str, times: float, x: int, y: int):
    image = pt.scale_by(pg.image.load(name), times)
    rect = image.get_rect()
    rect.topleft = (x, y)
    return image, rect


def save_score(score: int):
    with open('score', 'wb') as f:
        pickle.dump(score, f)


def save_equipment(eqpmt: dict):
    with open('eqpmt_dict', 'wb') as f:
        pickle.dump(eqpmt, f)


def saveAndExit(eqpmt: dict, score: int):
    save_score(score)
    save_equipment(eqpmt)
    sys.exit()


if __name__ == '__main__':
    game = Game()
    game.run()
